#region Copyright (C) Simon Bridewell
// 
// This file is part of the GifComponents library.
// GifComponents is free software; you can redistribute it and/or
// modify it under the terms of the Code Project Open License.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// Code Project Open License for more details.
// 
// You can read the full text of the Code Project Open License at:
// http://www.codeproject.com/info/cpol10.aspx
//
// GifComponents is a derived work based on NGif written by gOODiDEA.NET
// and published at http://www.codeproject.com/KB/GDI-plus/NGif.aspx,
// with an enhancement by Phil Garcia published at
// http://www.thinkedge.com/blogengine/post/2008/02/20/Animated-GIF-Encoder-for-NET-Update.aspx
//
// Simon Bridewell makes no claim to be the original author of this library,
// only to have created a derived work.
#endregion

using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;

namespace GifComponents.Components
{
	/// <summary>
	/// A data sub-block to form part of a Graphics Interchange Format data
	/// stream.
	/// See http://www.w3.org/Graphics/GIF/spec-gif89a.txt section 15.
	/// </summary>
	/// <remarks>
	/// Data Sub-blocks are units containing data. They do not have a label, 
	/// these blocks are processed in the context of control blocks, wherever 
	/// data blocks are specified in the format. The first byte of the Data 
	/// sub-block indicates the number of data bytes to follow. A data sub-block 
	/// may contain from 0 to 255 data bytes. The size of the block does not 
	/// account for the size byte itself, therefore, the empty sub-block is one 
	/// whose size field contains 0x00.
	/// </remarks>
	public class DataBlock : GifComponent
	{
		#region declarations
		private int _blockSize;
		private byte[] _data;
		#endregion
		
		#region constructor
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="blockSize">
		/// The number of bytes in the data block.
		/// </param>
		/// <param name="data">
		/// The bytes which make up the data in the data block.
		/// </param>
		public DataBlock( int blockSize, byte[] data )
		{
			SaveData( blockSize, data );
		}
		#endregion
		
		#region constructor( Stream )
		/// <summary>
		/// Reads the next variable length data block from the input stream.
		/// </summary>
		/// <param name="inputStream">
		/// The input stream to read.
		/// </param>
		public DataBlock( Stream inputStream ) : this( inputStream, false )
		{}
		#endregion
		
		#region constructor( Stream, bool )
		/// <summary>
		/// Reads the next variable length data block from the input stream.
		/// </summary>
		/// <param name="inputStream">
		/// The input stream to read.
		/// </param>
		/// <param name="xmlDebugging">Whether or not to create debug XML</param>
		public DataBlock( Stream inputStream, bool xmlDebugging )
			: base( xmlDebugging )
		{
			if( inputStream == null )
			{
				throw new ArgumentNullException( "inputStream" );
			}
			
			int blockSize = Read( inputStream );
			
			if( blockSize == -1 )
			{
				// then we're at the end of the stream
				SaveData( 0, new byte[0] );
				string message
					= "The end of the input stream was reached whilst "
					+ "attempting to read a DataBlock.";
				SetStatus( ErrorState.EndOfInputStream, message );
				WriteDebugXmlFinish();
				return;
			}
			
			int bytesRead = 0;
			byte[] buffer;
			buffer = new byte[blockSize];
			if( blockSize > 0 )
			{
				// keep reading until we've read the entire block
				int count = 0;
				while( bytesRead < blockSize ) 
				{
					count = inputStream.Read( buffer, bytesRead, blockSize - bytesRead );
					if( count == 0 )
					{
						// then we've reached the end of the file
						break;
					}
					bytesRead += count;
				}
			}

			SaveData( blockSize, buffer );
			
			WriteDebugXmlElement( "BlockSize", blockSize );
			WriteDebugXmlByteValues( "BytesRead", buffer );

			if( bytesRead < blockSize )
			{
				string message
					= "Supplied block size: " + blockSize
					+ ". Actual block size: " + bytesRead;
				SetStatus( ErrorState.DataBlockTooShort, message );
			}
			
			WriteDebugXmlFinish();
		}
		#endregion

		#region private SaveData method
		private void SaveData( int blockSize, byte[] data )
		{
			if( data == null )
			{
				throw new ArgumentNullException( "data" );
			}
			
			string message
				= "Supplied block size: " + blockSize
				+ ". Actual block size: " + data.Length;

			if( blockSize > data.Length )
			{
				SetStatus( ErrorState.DataBlockTooShort, message );
			}

			if( blockSize < data.Length )
			{
				SetStatus( ErrorState.DataBlockTooLong, message );
			}
			
			_blockSize = blockSize;
			_data = data;
		}
		#endregion
		
		#region DeclaredBlockSize property
		/// <summary>
		/// Gets the block size held in the first byte of this data block.
		/// This should be the same as the actual length of the data block but
		/// may not be if the data block was instantiated from a corrupt stream
		/// - check the ErrorStatus property.
		/// </summary>
		public int DeclaredBlockSize
		{
			get { return _blockSize; }
		}
		#endregion
		
		#region ActualBlockSize property
		/// <summary>
		/// Gets the actual length of the data block.
		/// </summary>
		public int ActualBlockSize
		{
			get { return _data.Length; }
		}
		#endregion
		
		#region Data property
		/// <summary>
		/// Gets the byte array containing the data in this data sub-block.
		/// This does not include the first byte which holds the block size.
		/// </summary>
		[SuppressMessage("Microsoft.Performance", 
		                 "CA1819:PropertiesShouldNotReturnArrays")]
		public byte[] Data
		{
			get { return _data; }
		}
		#endregion

		#region indexer
		/// <summary>
		/// Gets a specific byte within the data block
		/// </summary>
		public byte this[int index]
		{
			get
			{
				if( index >= _data.Length )
				{
					string message
						= "Supplied index: " + index
						+ ". Array length: " + _data.Length;
					throw new ArgumentOutOfRangeException( "index", message );
				}
				return _data[index];
			}
		}
		#endregion
		
		#region public WriteToStream method
		/// <summary>
		/// Writes this component to the supplied output stream.
		/// </summary>
		/// <param name="outputStream">
		/// The output stream to write to.
		/// </param>
		public override void WriteToStream( Stream outputStream )
		{
			WriteByte( _blockSize, outputStream );
			foreach( byte b in _data )
			{
				WriteByte( (int) b, outputStream );
			}
		}
		#endregion
	}
}
